package com.hexacta.hrs.domain.staffing;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;

public class Particionador {

    /**
     * Dado un objeto particionable por fecha, y dado un objeto base, cuyo rango
     * de fechas esta incluido en el rango de fechas del objeto particionable,
     * el metodo devuelve los objetos resultantes de la division por fechas del
     * objeto particionable.
     * 
     * Ej: si el objeto base tiene rango de fecha [15/01/2010,20/01/2010], y el
     * objeto a particionar tiene un rango de fechas [01/01/2010,31/01/2010], el
     * metodo devuelve dos objetos identicos a particionable pero con rangos de
     * fecha [01/01/2010,14/01/2010], [15/01/2010,20/01/2010] y
     * [21/01/2010,31/01/2010].
     * 
     * @param particionable
     *            : objeto que implementa la interfaz TParticionable.
     * @param base
     *            : objeto que implementa la interfaz TParticionable y su rango
     *            de fechas esta incluido en el rango de fechas del objeto
     *            particionable.
     */
    public static <TBase extends ParticionablePorFecha, TParticionable extends ParticionablePorFecha> Collection<TParticionable> particionar(
            final TParticionable particionable, final TBase base) {
        List<TParticionable> particiones = new ArrayList<TParticionable>();

        particiones.addAll(Particionador.particionarExcluyendoInterseccion(particionable, base));
        particiones.add(Particionador.obtenerInterseccionNoIncluido(particionable, base));

        return particiones;
    }

    /**
     * Dado un objeto particionable por fecha, y dado un objeto base, cuyo rango
     * de fechas esta incluido en el rango de fechas del objeto particionable,
     * el metodo devuelve los objetos resultantes de la division por fechas del
     * objeto particionable.
     * 
     * Ej: si el objeto base tiene rango de fecha [15/01/2010,20/01/2010], y el
     * objeto a particionar tiene un rango de fechas [01/01/2010,31/01/2010], el
     * metodo devuelve dos objetos identicos a particionable pero con rangos de
     * fecha [01/01/2010,14/01/2010] y [21/01/2010,31/01/2010].
     * 
     * @param particionable
     *            : objeto que implementa la interfaz TParticionable.
     * @param base
     *            : objeto que implementa la interfaz TParticionable y su rango
     *            de fechas esta incluido en el rango de fechas del objeto
     *            particionable.
     */
    public static <TBase extends ParticionablePorFecha, TParticionable extends ParticionablePorFecha> Collection<TParticionable> particionarExcluyendoInterseccion(
            final TParticionable particionable, final TBase base) {
        List<TParticionable> particiones = new ArrayList<TParticionable>();

        boolean fechaDeInicioMayorOIgual = particionable.getStartDate().compareTo(base.getStartDate()) >= 0;
        boolean fechaDeFinMenorOIgual = particionable.getEndDate().compareTo(base.getEndDate()) <= 0;
        boolean fechaDeInicioMenor = particionable.getStartDate().compareTo(base.getStartDate()) < 0;
        boolean fechaDeFinMayor = particionable.getEndDate().compareTo(base.getEndDate()) > 0;

        TParticionable particion;

        if (fechaDeInicioMenor && fechaDeFinMenorOIgual) {
            // Particion m�s chica.
            particion = obtenerParticionMenorAInterseccion(particionable, base);
            if (particion != null) {
                particiones.add(particion);
            }

            return particiones;
        }

        if (fechaDeInicioMenor && fechaDeFinMayor) {
            // Particion m�s chica.
            particion = obtenerParticionMenorAInterseccion(particionable, base);
            if (particion != null) {
                particiones.add(particion);
            }

            // Particion m�s grande.
            particion = obtenerParticionMayorAInterseccion(particionable, base);
            if (particion != null) {
                particiones.add(particion);
            }

            return particiones;
        }

        if (fechaDeInicioMayorOIgual && fechaDeFinMayor) {
            // Particion m�s grande.
            particion = obtenerParticionMayorAInterseccion(particionable, base);
            if (particion != null) {
                particiones.add(particion);
            }

            return particiones;
        }

        return particiones;
    }

    /**
     * Ej: si el objeto base tiene rango de fecha [15/01/2010,20/01/2010], y el
     * objeto a particionar tiene un rango de fechas [01/01/2010,31/01/2010], el
     * metodo devuelve un objeto identico a particionable pero con el rango de
     * fecha siguiente: [21/01/2010,31/01/2010].
     * 
     * @param particionable
     *            : objeto que implementa la interfaz TParticionable.
     * @param base
     *            : objeto que implementa la interfaz TParticionable y su rango
     *            de fechas esta incluido en el rango de fechas del objeto
     *            particionable.
     */
    public static <TBase extends ParticionablePorFecha, TParticionable extends ParticionablePorFecha> TParticionable obtenerParticionMayorAInterseccion(
            final TParticionable particionable, final TBase base) {
        TParticionable particion;
        try {
            particion = (TParticionable) particionable.clone();
            Calendar newStartDate = (Calendar) base.getEndDate().clone();
            newStartDate.add(Calendar.DAY_OF_YEAR, 1);
            particion.setStartDate(newStartDate);
            // particion.setStartDate(base.getEndDate());
            return particion;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * Dado un objeto particionable por fecha, y dado un objeto base, cuyo rango
     * de fechas esta incluido en el rango de fechas del objeto particionable,
     * el metodo devuelve un objeto identico a particionable pero con su rango
     * de fechas igual a la interseccion con el objeto base.
     * 
     * Ej: si el objeto base tiene rango de fecha [15/01/2010,20/01/2010], y el
     * objeto a particionar tiene un rango de fechas [01/01/2010,31/01/2010], el
     * metodo devuelve un objeto identico a particionable pero con el rango de
     * fecha siguiente: [15/01/2010,20/01/2010].
     * 
     * @param particionable
     *            : objeto que implementa la interfaz TParticionable.
     * @param base
     *            : objeto que implementa la interfaz TParticionable y su rango
     *            de fechas esta incluido en el rango de fechas del objeto
     *            particionable.
     */
    public static <TBase extends ParticionablePorFecha, TParticionable extends ParticionablePorFecha> TParticionable obtenerInterseccion(
            final TParticionable particionable, final TBase base) {

        boolean fechaDeInicioIgual = particionable.getStartDate().compareTo(base.getStartDate()) == 0;
        boolean fechaDeFinIgual = particionable.getEndDate().compareTo(base.getEndDate()) == 0;

        if (fechaDeInicioIgual && fechaDeFinIgual)
            return particionable;

        try {
            TParticionable particion = (TParticionable) particionable.clone();
            // particion.setStartDate((Calendar) base.getStartDate().clone());
            // particion.setEndDate((Calendar) base.getEndDate().clone());
            particion.setStartDate(base.getStartDate());
            particion.setEndDate(base.getEndDate());
            return particion;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Ej: si el objeto base tiene rango de fecha [15/01/2010,20/01/2010], y el
     * objeto a particionar tiene un rango de fechas [01/01/2010,31/01/2010], el
     * metodo devuelve un objeto identico a particionable pero con el rango de
     * fecha siguiente: [01/01/2010,14/01/2010].
     * 
     * @param particionable
     *            : objeto que implementa la interfaz TParticionable.
     * @param base
     *            : objeto que implementa la interfaz TParticionable y su rango
     *            de fechas esta incluido en el rango de fechas del objeto
     *            particionable.
     */
    public static <TBase extends ParticionablePorFecha, TParticionable extends ParticionablePorFecha> TParticionable obtenerParticionMenorAInterseccion(
            final TParticionable particionable, final TBase base) {
        TParticionable particion;
        try {
            particion = (TParticionable) particionable.clone();
            Calendar newEndDate = (Calendar) base.getStartDate().clone();
            newEndDate.add(Calendar.DAY_OF_YEAR, -1);
            particion.setEndDate(newEndDate);
            // particion.setEndDate(base.getStartDate());
            return particion;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Dado un objeto particionable por fecha, y dado un objeto base, cuyo rango
     * de fechas NO esta incluido en el rango de fechas del objeto
     * particionable, el metodo devuelve un objeto identico a particionable pero
     * con su rango de fechas igual a la interseccion con el objeto base.
     * 
     * Ej: si el objeto base tiene rango de fecha [15/01/2010,31/01/2010], y el
     * objeto a particionar tiene un rango de fechas [01/01/2010,20/01/2010], el
     * metodo devuelve un objeto identico a particionable pero con el rango de
     * fecha siguiente: [15/01/2010,20/01/2010].
     * 
     * @param particionable
     *            : objeto que implementa la interfaz TParticionable.
     * @param base
     *            : objeto que implementa la interfaz TParticionable y su rango
     *            de fechas esta incluido en el rango de fechas del objeto
     *            particionable.
     */
    public static <TBase extends ParticionablePorFecha, TParticionable extends ParticionablePorFecha> TParticionable obtenerInterseccionNoIncluido(
            final TParticionable particionable, final TBase base) {

        boolean fechaDeInicioIgual = particionable.getStartDate().compareTo(base.getStartDate()) == 0;
        boolean fechaDeFinIgual = particionable.getEndDate().compareTo(base.getEndDate()) == 0;

        if (fechaDeInicioIgual && fechaDeFinIgual)
            return particionable;

        Calendar startDate;
        if (particionable.getStartDate().before(base.getStartDate())) {
            startDate = base.getStartDate();
        } else {
            startDate = particionable.getStartDate();
        }

        Calendar endDate;
        if (particionable.getEndDate().before(base.getEndDate())) {
            endDate = particionable.getEndDate();
        } else {
            endDate = base.getEndDate();
        }

        try {
            TParticionable particion = (TParticionable) particionable.clone();
            particion.setStartDate(startDate);
            particion.setEndDate(endDate);
            return particion;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
